<template>
	<view class="wrapper">
		<view class="header">
			<up-navbar placeholder autoBack bgColor="rgba(0,0,0,0)" left-icon-size="24">
				<template #center>
					<navigator hover-class="none" :url="`/pages/detail/aiDetail?botId=`+botId" class="center">
						<div class="mr5 mb5 fbold f32">{{botInfo.name}}</div>
						<up-icon name="arrow-right"></up-icon>
					</navigator>
				</template>
			</up-navbar>
			<!-- <image src="../../static/index/个人中心@2x.png" mode="widthFix" class="avatar posf"
				@click="open('../user/user')">
			</image>
			<image src="../../static/index/创建会话@3x.png" mode="widthFix" class="new-chat posf" @click="onNewChat">
			</image> -->

		</view>
		<!-- <image :src="logoUrl" mode="heightFix" class="logo" @click="goIndex"></image> -->
		<scroll-view :scroll-top="scrollTop" scroll-with-animation scroll-y="true" class="main posr">
			<div @touchmove="viewTouch" class="scroll-inner" id="scrollInner">
				<view style="text-align: center;margin: 20rpx 0 30rpx 0;">
					<image :src="botInfo.iconUrl" mode="widthFix" style="width: 300rpx; height: 300rpx;"
						class="bor50" />
				</view>
				<div class="mb20 duihua">
					<!-- 回复内容 -->
					<div class="answer-box c333 prologue-box">
						<div class="text" :style="`font-size:${fontSize};`">{{botInfo.prologue}}</div>
						<!-- <div class="flex-start">
							<image src="../../static/窗口@2x.png" mode="widthFix" class="copy-icon mr38"
								@click="copy(botInfo.prologue)"></image>
							<image v-if="!isGenerate&&isPlay&&choiceIndex===-1"
								@click="textToSpeech(botInfo.prologue,-1)" src="../../static/语音播放.png" mode="widthFix"
								class="voice-icon " :class="{isPlay:choiceIndex===-1}">
							</image>
							<div v-else-if="isGenerate&&choiceIndex===-1">
								<div class="f24 c333">语音生成中...</div>
							</div>
							<image v-else @click="textToSpeech(botInfo.prologue,-1)" src="../../static/语音.png"
								mode="widthFix" class="voice-icon " :class="{isPlay:choiceIndex===-1}">
							</image>
						</div> -->
					</div>
				</div>
				<view v-if="suggestedQuestions.length>0" class="mb10">
					<view v-for="item in suggestedQuestions" :key="item">
						<view @click="send(item,'')" class="mb20 bfff answer-box prompt-box">
							<div class="text">
								{{item}}
							</div>
						</view>
					</view>

				</view>
				<view :id="'view'+i" v-for="(v,i) in msgList" :key="i">
					<!-- 我发 -->
					<view class="flex-end cfff mb8 duihua" v-if="v.from===0">
						<!-- 图片 -->
						<div class="pic-box mb28 center" v-if="v.type=='image'">
							<image @click="prevImg(v.content)" :src="v.content" mode="widthFix" class="cover-pic">
							</image>
						</div>
						<!-- 文字 -->
						<!-- <rich-text class="f30 t-box mb28 text" @click="copy(v.content)" :selectable="true" v-else
						:style="`font-size:${fontSize};`">{{v.content}}</rich-text> -->
						<text style="max-width: 80%;" class="f30 t-box mb28 text" user-select="true" v-else
							:style="`font-size:${fontSize};`">{{v.content}}</text>
					</view>
					<!-- Ai回复 -->
					<div class="mb50 duihua" v-else-if="v.from===1">
						<!-- 思考中。。。 -->
						<!-- <div class="flex-start mb15" v-if="v.status=='loading'">
							<view class="f28 c333 flex1 nowrap">思考并回答中...</view>
							<image src="../../static/停止图标@2x.png" mode="widthFix" class="stop-icon mr10"></image>
							<div class="f28 c333">停止生成</div>
						</div> -->
						<!-- 已回复 -->
						<!-- <div class="flex-start mb15" v-else>
						<image :src="logoUrl" mode="widthFix" class="t-avatar mr15"></image>
					</div> -->
						
						<!-- loading -->
						<div class="bfff answer-box c333 answer-box-new" v-if="v.markdownContent==''||v.content==''">
							<image src="/static/load.gif" mode="widthFix" class="load-icon mt15"></image>
						</div>
						<!-- 回复内容 -->
						<div class="bfff answer-box c333 answer-box-new w100" v-else>
							<!-- <div class="f30 mb30 text" :style="`font-size:${fontSize};`">{{v.content}}</div> -->

							<towxml v-if="v.markdownContent!==''" class="f30 text" :style="`font-size:${fontSize};`"
								:nodes="v.markdownContent">
							</towxml>

							<div v-else class="f30 mb30 text" :style="`font-size:${fontSize};`">{{v.content}}</div>
							<div class="flex-start mt10 mb30" v-show="v.status==='finish'">
								<image src="../../static/窗口@2x.png" mode="widthFix" class="copy-icon mr38"
									@click="copy(v.content)"></image>
								<image v-if="!isGenerate&&isPlay&&choiceIndex===i" @click="textToSpeech(v.content,i)"
									src="../../static/语音播放.png" mode="widthFix" class="voice-icon "
									:class="{isPlay:choiceIndex===i}">
								</image>
								<div v-else-if="isGenerate&&choiceIndex===i">
									<div class="f24 c333">语音生成中...</div>
								</div>
								<image v-else @click="textToSpeech(v.content,i)" src="../../static/语音.png"
									mode="widthFix" class="voice-icon " :class="{isPlay:choiceIndex===i}">
								</image>
							</div>
						</div>
					</div>
					<div v-else class="mb50" style="font-size: 12px;color:#ccc;text-align: center;">
						<p>{{v.content}}</p>
					</div>
				</view>
			</div>
		</scroll-view>
		<bottomSend @send='send'></bottomSend>
	</view>
</template>

<script setup>
	import {
		baseUrl
	} from '../../utils/config.js'

	import {
		newConversation,
		createSpeech,
		cancelChat
	} from '../../apis/boot.js'
	import {
		info
	} from '../../apis/botinfo.js'
	import {
		ChunkRes
	} from 'chunk-res'

	// #ifdef MP
	let plugin = requirePlugin("WechatSI")
	// #endif
	import bottomSend from '@/components/bottomSend/bottomSend';
	import {
		onLoad,
		onShow,
		onHide
	} from "@dcloudio/uni-app";
	import useAiStore from '@/store/modules/index'
	import {
		storeToRefs
	} from 'pinia';
	import {
		ref,
		getCurrentInstance,
		nextTick
	} from 'vue'

	const logoUrl = ref('')
	// const chatUrl = ref('/ooh-ai/v3/bot/chatStreamingMessage')
	const chatUrl = ref('/ooh-ai/v3/bot/chatFlow')
	// 消息列表
	const msgList = ref([])

	const aiStore = useAiStore()
	const {
		fontSize
	} = storeToRefs(aiStore)

	const {
		proxy,
		appContext
	} = getCurrentInstance()

	const chunkRes = ChunkRes();
	const isPlay = ref(false)
	const choiceIndex = ref(-2)

	const isGenerate = ref(false)

	const botId = ref('')
	const botInfo = ref({})
	const suggestedQuestions = ref([])

	// const userId = ref(null)
	const tempAnswer = ref({
		from: 1,
		type: 'text',
		content: '',
		markdownContent: '',
		status: 'finish',
	})
	const requestTask = ref(null)
	const chatId = ref('')
	let innerAudioContext = null
	// 文字转语音
	async function textToSpeech(content, index) {

		// 如果正在播放暂停当前播放
		if (innerAudioContext) {
			innerAudioContext.stop()
			// 如果点击的是正在播放的,则停止
			if (choiceIndex.value === index) {
				isPlay.value = false
				choiceIndex.value = -2
				return
			}
		}
		choiceIndex.value = index
		isGenerate.value = true
		innerAudioContext = wx.createInnerAudioContext()

		// 替换掉长文本的特殊字符
		// const langText = content.replace(/\r/g, "").replace(/n/g, "").replace(/\s+/g, "").replace(/#/g, "").replace(
		// 	/-/g, "").replace(/https?:\/\/[^\s]+/g, '')
		const langText = content.replace(/#/g, "").replace(/-/g, "").replace(/https?:\/\/[^\s]+/g, '')


		const result = await createSpeech({
			text: langText
		})
		if (result.success) {
			innerAudioContext.src = result.data

			isGenerate.value = false

			innerAudioContext.play()
			isPlay.value = true
			innerAudioContext.onEnded(() => {
				innerAudioContext = null
				isPlay.value = false
				choiceIndex.value = -2
			})
		} else {
			uni.showToast({
				title: '语音播放失败，请稍后重试',
				icon: 'error'
			})
		}

		// if (langText.length <= 300) {
		// 	startPlay(langText, index)
		// } else {
		// 	// 拆分成多段
		// 	const num = Math.ceil(langText.length / 300)
		// 	for (let i = 0; i < num; i++) {
		// 		const text = langText.substr(0 + i * 300, 300)
		// 		setTimeout(() => {
		// 			startPlay(text, index)
		// 		}, 75 * 1000 * i)
		// 	}
		// }
	}

	/**
	 * 开始播放文字内容
	 * @param {Object} content播放的文字内容
	 */
	function startPlay(content, index) {
		plugin.textToSpeech({
			lang: "zh_CN",
			tts: true,
			content,
			success: function(res) {
				// innerAudioContext = wx.createInnerAudioContext()
				innerAudioContext.src = res.filename
				innerAudioContext.play()
				isPlay.value = index
				innerAudioContext.onEnded(() => {
					innerAudioContext = null
					isPlay.value = false
				})
			},
			fail: function(res) {
				// console.log("fail tts", res)
				uni.showToast({
					title: '语音播放失败，请稍后重试',
					icon: 'error'
				})
			}
		})
	}

	// 发送事件

	function send(text, imgUrl) {
		if (tempAnswer.value.status !== 'finish') {
			uni.showToast({
				title: '请等我回答完再提问',
				icon: 'none'
			})
			return false
		}
		// if (msgList.value.length && tempAnswer.value.status !== 'finish') {
		// 	// 发送问题前判断上次对话是否结束
		// 	requestTask.value.abort()
		// 	abortRequest(text, imgUrl)
		// } else {
		// 第一次对话或者上次对话结束
		if (imgUrl) {
			imgUrl = imgUrl + '?x-oss-process=image/resize,w_980'
			msgList.value.push({
				from: 0, //0我 1小欧 -1系统
				type: 'image', //text image
				content: imgUrl, //正文
				markdownContent: '',
				status: 'finish', //loading finish
			})
		}
		if (text) {
			msgList.value.push({
				from: 0, //0我 1小欧 -1系统
				type: 'text', //text image
				content: text, //正文,
				markdownContent: '',
				status: 'finish', //loading finish
			})
		}
		getConversationId(text, imgUrl)
		// }
		goScroll()
	}

	async function abortRequest(content, imgUrl = '') {
		const conversationStorage = uni.getStorageSync('conversation')
		const conversation = conversationStorage && conversationStorage !== '' ? conversationStorage : null
		if (conversation && requestTask.value !== null && chatId.value) {
			const postData = {
				chatId: chatId.value,
				conversationId: conversation.data
			}
			const result = await cancelChat(postData)
			if (result.success) {
				// requestTask.value.abort()
				if (imgUrl) {
					imgUrl = imgUrl + '?x-oss-process=image/resize,w_980'
					msgList.value.push({
						from: 0, //0我 1小欧 -1系统
						type: 'image', //text image
						content: imgUrl, //正文
						markdownContent: '',
						status: 'finish', //loading finish
					})
				}
				if (content) {
					msgList.value.push({
						from: 0, //0我 1小欧 -1系统
						type: 'text', //text image
						content: content, //正文,
						markdownContent: '',
						status: 'finish', //loading finish
					})
				}
				getAnswer(conversation.data, content, imgUrl)
			}

		}

	}

	async function getConversationId(content, imgUrl = '') {
		const conversationStorage = uni.getStorageSync('conversation')
		const conversation = conversationStorage && conversationStorage !== '' ? conversationStorage : null
		const nowTime = new Date().valueOf()
		if (!conversation || Number(conversation.expireTime) < nowTime) {
			// 不存在conversation或者过期
			const postData = {
				botId: botId.value
			}
			const result = await newConversation(postData)
			if (result && result.success) {
				const curTime = new Date()
				const storage = {
					data: result.data,
					expireTime: curTime.setMinutes(curTime.getMinutes() + 60)
				}
				uni.setStorageSync('conversation', storage)

				getAnswer(result.data, content, imgUrl)
				// 开启定时判断conversation是否过期
				checkConversation()
			}
		} else {
			getAnswer(conversation.data, content, imgUrl)
		}
	}
	// 用户手动滚动
	let canAutoScroll = true
	let timeObj = ''

	function viewTouch(e) {
		if (timeObj) {
			clearTimeout(timeObj)
		}
		canAutoScroll = false
		timeObj = setTimeout(() => {
			canAutoScroll = true
		}, 3000)
	}
	/**
	 * 获取智能体回答
	 */
	function getAnswer(conversationId, content, imageUrl = '') {
		const that = this
		tempAnswer.value = {
			from: 1,
			type: 'text',
			content: '',
			markdownContent: '',
			status: 'loading',
		}
		let answerContent = ''
		msgList.value.push(tempAnswer.value)

		let userInfo = uni.getStorageSync('userinfo')
		const token = `${userInfo.jwt.token_type} ${userInfo.jwt.access_token}`
		const postData = {
			botId: botId.value,
			conversationId: conversationId,
			workflowId: botInfo.value.workflowId,
			query: content,
			imageUrl: imageUrl
		}
		requestTask.value = uni.request({
			url: baseUrl + chatUrl.value,
			timeout: 60000,
			header: {
				'content-type': 'application/x-www-form-urlencoded',
				'ooh-auth': token
			},
			responseType: 'text',
			method: 'POST',
			enableChunked: true,
			data: postData,
			success: (result) => {
				console.log('finish')
				tempAnswer.value.status = 'finish'
				const lastResTexts = chunkRes.onComplateReturn()
				if (lastResTexts && lastResTexts.length) {
					lastResTexts.forEach((item) => {
						const jsonData = JSON.parse(item)
						if (jsonData.event === 'conversation.message.completed' && jsonData.message
							.type === 'answer') {
							answerContent = jsonData.message.content
							tempAnswer.value.type = jsonData.message.content_type
						}
					})
					// 将返回内容转义markdown

					tempAnswer.value.content = answerContent
					tempAnswer.value.markdownContent = formartMarkdown(answerContent)
				}
				if (canAutoScroll) {
					goScroll()
				}

			}
		})

		requestTask.value.onHeadersReceived(function(res) {
			// console.log('Headers:', res.header)
		})

		requestTask.value.onChunkReceived((res) => {
			// const tempData = new Uint8Array(res.data)
			// const textDecoder = new TextDecoder('utf-8')
			// const tempText = textDecoder.decode(tempData)
			// console.log('server:', tempText)
			const resTexts = chunkRes.onChunkReceivedReturn(res.data);
			if (resTexts && resTexts.length) {

				resTexts.forEach((item) => {
					const jsonData = JSON.parse(item)
					if (jsonData.event === 'conversation.message.delta') {
						answerContent += jsonData.message.content
						tempAnswer.value.type = jsonData.message.content_type
						chatId.value = jsonData.message.chat_id

					}
				})
				// 将返回内容转义markdown

				tempAnswer.value.content = answerContent
				tempAnswer.value.markdownContent = formartMarkdown(answerContent)
				if (canAutoScroll) {
					goScroll()
				}
			}
		})
	}

	/**
	 * 将markdown格式文本转换成markdown内容
	 * @param {Object} content markdown格式文本
	 */
	function formartMarkdown(content) {
		let markdownContent = appContext.config.globalProperties.$towxml(content, 'markdown', {
			events: {
				tap: (event) => {
					const tapData = event.currentTarget.dataset.data

					if (tapData && tapData.tag === 'img') {
						// 预览图片
						uni.previewImage({
							urls: [tapData.attrs.src]
						})
					}
					if (tapData && tapData.tag === 'navigator') {
						uni.showToast({
							title: '文档正在打开',
							icon: 'loading',
							duration: 60000
						})
						// 打开链接
						uni.downloadFile({
							url: tapData.attrs.href,
							success(res) {
								const filePath = res.tempFilePath
								uni.openDocument({
									filePath: filePath,
									showMenu: true,
									success: function() {
										uni.showToast({
											title: '文档打开成功',
											icon: 'success',
											duration: 2000
										})
									},
									fail: function() {
										uni.showToast({
											title: '文档打开失败',
											icon: 'error',
											duration: 2000
										})
									}
								})
							},
							fail(res) {
								uni.showToast({
									title: '请复制该链接到浏览器内打开。',
									icon: 'none',
									duration: 2000
								})
							}
						})
					}
				}
			}
		})
		return markdownContent
	}

	// 滚动到最新消息事件
	const scrollTop = ref(0)

	function goScroll() {
		nextTick(() => {
			let viewDetail = uni.createSelectorQuery().select('#scrollInner').boundingClientRect(data => {
				scrollTop.value = data.height + 100
			}).exec()
		})
	}

	// 复制事件
	function copy(value) {
		uni.setClipboardData({
			data: value,
			success() {
				//调用方法成功
				// console.log('success');
			}
		})
	}

	// 图片预览
	function prevImg(url) {
		uni.previewImage({
			urls: [url]
		})
	}

	/**
	 * 定时检查conversation是否过期
	 */
	function checkConversation() {
		let timer = setInterval(() => {
			const conversationStorage = uni.getStorageSync('conversation')
			const conversation = conversationStorage && conversationStorage !== '' ? conversationStorage :
				null
			const nowTime = new Date().valueOf()
			if (!conversation || Number(conversation.expireTime) < nowTime) {
				msgList.value.push({
					from: -1,
					type: 'text',
					content: '当前会话已结束',
					status: 'finish',
				})
				clearInterval(timer)
			}
		}, 3605 * 1000)
	}

	function onNewChat() {
		msgList.value = []
		uni.removeStorageSync('conversation')
	}

	function goIndex() {
		uni.navigateBack()
	}
	async function getBotInfo() {
		const result = await info({
			botId: botId.value
		})
		if (result && result.success) {
			botInfo.value = result.data
			if (botInfo.value.workflowId !== '-1') {
				chatUrl.value = '/ooh-ai/v3/bot/chatFlow'
			}
			if (botInfo.value.suggestedQuestions.trim() !== '') {
				suggestedQuestions.value = botInfo.value.suggestedQuestions.split("|")
			}
		}
	}

	// 接收上个页面参数并自动发送
	onLoad((option) => {
		if (option.botId) {
			botId.value = option.botId
			getBotInfo()
			// userId.value = option.uid
			chatUrl.value = '/ooh-ai/v3/bot/chatStreamingMessage'
			// msgList.value.push({
			// 	from: 1, //0我 1小欧 -1系统
			// 	type: 'text', //text image
			// 	content: 'Hi，我叫小欧，我们是专为地铁沿线商家服务的AI平台，您只要告诉我您的名字（商户品牌），我就带您来趟地铁广告之旅。', //正文,
			// 	markdownContent: '',
			// 	status: 'finish', //loading finish
			// })
			// uni.removeStorageSync('conversation')
		} else {
			// userId.value = null
			if (option.text || option.imgUrl) {
				send(option.text, option.imgUrl)
			}
		}


	});

	onShow(() => {

	});
	onHide(() => {
		if (requestTask.value) {
			requestTask.value.abort()
		}

	});
</script>

<style lang="scss" scoped>
	.main {
		// background: radial-gradient(0% 0% at 0% 0%, #F5F5F5 27%, #FFF8F3 100%);
		// border-radius: 35px 35px 0px 0px;
		// background-color: #f7f7f7;
		background-color: #ffffff;
		height: calc(100vh - 100rpx);
		padding: 30rpx 20rpx 250rpx;



		.isPlay {
			animation: scale-breathe 2s infinite linear;
		}

		@keyframes scale-breathe {
			0% {
				transform: scale(1);
			}

			25% {
				transform: scale(1.2);
			}

			75% {
				transform: scale(0.8);
			}

			100% {
				transform: scale(1);
			}
		}

		.duihua {

			// max-width: 80%;
			// display: inline-block;
			padding: 0 4rpx;

			.text {
				display: inline-block;
			}
		}

		.pic-box {
			width: 280rpx;
			background: #FFFFFF;
			box-shadow: 3rpx 0rpx 8rpx 0rpx rgba(198, 198, 198, .4);
			border-radius: 25rpx 25rpx 25rpx 25rpx;
			padding: 20rpx;
			overflow: hidden;

			.cover-pic {
				width: 256rpx;
				height: auto;
				border-radius: 25rpx 25rpx 25rpx 25rpx;
			}
		}


		.t-box {
			background: #EE7C4B;
			box-shadow: 3rpx 0rpx 21rpx 0rpx rgba(198, 198, 198, .4);
			border-radius: 25rpx 0rpx 25rpx 25rpx;
			// padding: 35rpx;
			padding: 25rpx 32rpx;
			display: inline-block;
		}

		.t-avatar {
			// width: 60rpx;
			// height: 60rpx;
			width: 48rpx;
			height: 48rpx;
		}

		.load-icon {
			width: 150rpx;
		}

		.answer-box {
			// width: 725rpx;
			// height: 245rpx;
			// max-width: 80%;
			padding: 30rpx 30rpx 30rpx;
			box-shadow: 3rpx 0rpx 8rpx 0rpx rgba(198, 198, 198, .4);
			border-radius: 0rpx 25rpx 25rpx 25rpx;
			display: inline-block;

			.copy-icon {
				height: 34rpx;
				width: 34rpx;
			}

			.voice-icon {
				width: 28rpx;
				height: 36rpx;
			}
		}

		.prompt-box {
			border: 2rpx solid #cfcfcf;
			box-shadow: none;
			border-radius: 25rpx;
			font-size: 26rpx;
			padding: 20rpx;
		}

		.prologue-box {
			background-color: #f7f7f7;
			box-shadow: none;
			font-size: 24rpx;
			padding: 20rpx;
			border-radius: 25rpx;
		}

		.answer-box-new {
			padding: 10rpx 30rpx;
		}

		.stop-icon {
			width: 32rpx;
			height: 32rpx;
		}
	}

	.header {
		width: 100vw;
		height: 180rpx;
		display: block;
		// margin: 57rpx 0 -52rpx 162rpx;
		margin: 57rpx 0 -52rpx 0;
	}

	.avatar {
		width: 60rpx;
		height: 60rpx;
		left: 80rpx;
		top: 100rpx;
	}

	.new-chat {
		width: 60rpx;
		height: 60rpx;
		border-radius: 30rpx;
		left: 140rpx;
		top: 100rpx;
	}
</style>